diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index b571530c5d9..20183785dfd 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1211,7 +1211,7 @@ namespace ts { bind(node.statement); popActiveLabel(); if (!activeLabel.referenced && !options.allowUnusedLabels) { - errorOrSuggestionOnFirstToken(unusedLabelIsError(options), node, Diagnostics.Unused_label); + errorOrSuggestionOnNode(unusedLabelIsError(options), node.label, Diagnostics.Unused_label); } if (!node.statement || node.statement.kind !== SyntaxKind.DoStatement) { // do statement sets current flow inside bindDoStatement @@ -1918,9 +1918,16 @@ namespace ts { file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, message, arg0, arg1, arg2)); } - function errorOrSuggestionOnFirstToken(isError: boolean, node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any) { - const span = getSpanOfTokenAtPosition(file, node.pos); - const diag = 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 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); } @@ -2792,7 +2799,7 @@ namespace ts { node.declarationList.declarations.some(d => !!d.initializer) ); - errorOrSuggestionOnFirstToken(isError, node, Diagnostics.Unreachable_code_detected); + eachUnreachableRange(node, (start, end) => errorOrSuggestionOnRange(isError, start, end, Diagnostics.Unreachable_code_detected)); } } } @@ -2800,6 +2807,38 @@ namespace ts { } } + 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) && + // `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 hasModifier(s, ModifierFlags.Const); + default: + return false; + } + } + /* @internal */ export function isExportsOrModuleExportsOrAlias(sourceFile: SourceFile, node: Expression): boolean { return isExportsIdentifier(node) || diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e006a24c6f3..5ed4573c9b4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1059,6 +1059,7 @@ namespace ts { // 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; @@ -1069,7 +1070,7 @@ namespace ts { } const container = getEnclosingBlockScopeContainer(declaration); - return isInTypeQuery(usage) || isUsedInFunctionOrInstanceProperty(usage, declaration, container); + return !!(usage.flags & NodeFlags.JSDoc) || isInTypeQuery(usage) || isUsedInFunctionOrInstanceProperty(usage, declaration, container); function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean { const container = getEnclosingBlockScopeContainer(declaration); @@ -10367,9 +10368,9 @@ namespace ts { } } - if (!issuedElaboration && (length(targetProp && targetProp.declarations) || length(target.symbol && target.symbol.declarations))) { + if (!issuedElaboration && (targetProp && length(targetProp.declarations) || target.symbol && length(target.symbol.declarations))) { addRelatedInfo(reportedDiag, createDiagnosticForNode( - targetProp ? targetProp.declarations[0] : target.symbol.declarations[0], + targetProp && length(targetProp.declarations) ? targetProp.declarations[0] : target.symbol.declarations[0], 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) @@ -19345,6 +19346,38 @@ namespace ts { return resolveErrorCall(node); } + 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 mixinCount = countWhere(types, isMixinConstructorType); + let i = 0; + for (const intersectionMember of (firstBase as IntersectionType).types) { + i++; + // We want to ignore mixin ctors + if (mixinCount === 0 || mixinCount === types.length && i === 0 || !isMixinConstructorType(intersectionMember)) { + if (getObjectFlags(intersectionMember) & (ObjectFlags.Class | ObjectFlags.Interface)) { + if (intersectionMember.symbol === target) { + return true; + } + if (typeHasProtectedAccessibleBase(target, intersectionMember as InterfaceType)) { + return true; + } + } + } + } + return false; + } + if (firstBase.symbol === target) { + return true; + } + return typeHasProtectedAccessibleBase(target, firstBase as InterfaceType); + } + function isConstructorAccessible(node: NewExpression, signature: Signature) { if (!signature || !signature.declaration) { return true; @@ -19364,16 +19397,10 @@ namespace ts { // 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) { + if (containingClass && modifiers & ModifierFlags.Protected) { const containingType = getTypeOfNode(containingClass); - let baseTypes = getBaseTypes(containingType as InterfaceType); - while (baseTypes.length) { - const baseType = baseTypes[0]; - if (modifiers & ModifierFlags.Protected && - baseType.symbol === declaration.parent.symbol) { - return true; - } - baseTypes = getBaseTypes(baseType as InterfaceType); + if (typeHasProtectedAccessibleBase(declaration.parent.symbol, containingType as InterfaceType)) { + return true; } } if (modifiers & ModifierFlags.Private) { @@ -21102,7 +21129,7 @@ namespace ts { getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], UnionReduction.Subtype) : leftType; case SyntaxKind.EqualsToken: - const special = getSpecialPropertyAssignmentKind(left.parent as BinaryExpression); + const special = isBinaryExpression(left.parent) ? getSpecialPropertyAssignmentKind(left.parent) : SpecialPropertyAssignmentKind.None; checkSpecialAssignment(special, right); if (isJSSpecialPropertyAssignment(special)) { return leftType; diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 87b425f6ed4..548d4c18c53 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -43,7 +43,7 @@ namespace ts { if (sourceFile.kind === SyntaxKind.Bundle) { const jsFilePath = options.outFile || options.out!; const sourceMapFilePath = getSourceMapFilePath(jsFilePath, options); - const declarationFilePath = (forceDtsPaths || options.declaration) ? removeFileExtension(jsFilePath) + Extension.Dts : undefined; + const declarationFilePath = (forceDtsPaths || getEmitDeclarations(options)) ? removeFileExtension(jsFilePath) + Extension.Dts : undefined; const declarationMapPath = getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined; const bundleInfoPath = options.references && jsFilePath ? (removeFileExtension(jsFilePath) + infoExtension) : undefined; return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, bundleInfoPath }; @@ -53,7 +53,7 @@ namespace ts { const sourceMapFilePath = isJsonSourceFile(sourceFile) ? undefined : getSourceMapFilePath(jsFilePath, options); // For legacy reasons (ie, we have baselines capturing the behavior), js files don't report a .d.ts output path - this would only matter if `declaration` and `allowJs` were both on, which is currently an error const isJs = isSourceFileJavaScript(sourceFile); - const declarationFilePath = ((forceDtsPaths || options.declaration) && !isJs) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined; + const declarationFilePath = ((forceDtsPaths || getEmitDeclarations(options)) && !isJs) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined; const declarationMapPath = getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined; return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, bundleInfoPath: undefined }; } @@ -205,7 +205,7 @@ namespace ts { // Setup and perform the transformation to retrieve declarations from the input files const nonJsFiles = filter(sourceFiles, isSourceFileNotJavaScript); const inputListOrBundle = (compilerOptions.outFile || compilerOptions.out) ? [createBundle(nonJsFiles, !isSourceFile(sourceFileOrBundle) ? sourceFileOrBundle.prepends : undefined)] : nonJsFiles; - if (emitOnlyDtsFiles && !compilerOptions.declaration) { + 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 nonJsFiles.forEach(collectLinkedAliases); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 75cdcaea328..5c34e4115a1 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -2329,7 +2329,7 @@ namespace ts { if (!sourceFile.isDeclarationFile) { const absoluteSourceFilePath = host.getCanonicalFileName(getNormalizedAbsolutePath(sourceFile.fileName, currentDirectory)); if (absoluteSourceFilePath.indexOf(absoluteRootDirectoryPath) !== 0) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, sourceFile.fileName, options.rootDir)); + programDiagnostics.add(createCompilerDiagnostic(Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, sourceFile.fileName, rootDirectory)); allFilesBelongToPath = false; } } @@ -2500,7 +2500,7 @@ namespace ts { } } - if (options.declarationMap && !options.declaration) { + if (options.declarationMap && !getEmitDeclarations(options)) { createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "declarationMap", "declaration"); } diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index d5f574762a1..d05d8350508 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -81,6 +81,7 @@ namespace ts { let filesWithInvalidatedResolutions: Map | undefined; let filesWithInvalidatedNonRelativeUnresolvedImports: Map> | undefined; let allFilesHaveInvalidatedResolution = false; + const nonRelativeExternalModuleResolutions = createMultiMap(); const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory!()); // TODO: GH#18217 const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost(); @@ -154,6 +155,7 @@ namespace ts { function clear() { clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf); customFailedLookupPaths.clear(); + nonRelativeExternalModuleResolutions.clear(); closeTypeRootsWatch(); resolvedModuleNames.clear(); resolvedTypeReferenceDirectives.clear(); @@ -199,19 +201,20 @@ namespace ts { perDirectoryResolvedModuleNames.clear(); nonRelaticeModuleNameCache.clear(); perDirectoryResolvedTypeReferenceDirectives.clear(); + nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions); + nonRelativeExternalModuleResolutions.clear(); } function finishCachingPerDirectoryResolution() { allFilesHaveInvalidatedResolution = false; filesWithInvalidatedNonRelativeUnresolvedImports = undefined; + clearPerDirectoryResolutions(); directoryWatchesOfFailedLookups.forEach((watcher, path) => { if (watcher.refCount === 0) { directoryWatchesOfFailedLookups.delete(path); watcher.watcher.close(); } }); - - clearPerDirectoryResolutions(); } function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): CachedResolvedModuleWithFailedLookupLocations { @@ -275,7 +278,7 @@ namespace ts { perDirectoryResolution.set(name, resolution); } resolutionsInFile.set(name, resolution); - watchFailedLookupLocationOfResolution(resolution); + watchFailedLookupLocationsOfExternalModuleResolutions(name, resolution); if (existingResolution) { stopWatchFailedLookupLocationOfResolution(existingResolution); } @@ -441,18 +444,27 @@ namespace ts { return fileExtensionIsOneOf(path, failedLookupDefaultExtensions); } - function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) { + function watchFailedLookupLocationsOfExternalModuleResolutions(name: string, resolution: ResolutionWithFailedLookupLocations) { // No need to set the resolution refCount - if (!resolution.failedLookupLocations || !resolution.failedLookupLocations.length) { - return; + if (resolution.failedLookupLocations && resolution.failedLookupLocations.length) { + if (resolution.refCount) { + resolution.refCount++; + } + else { + resolution.refCount = 1; + if (isExternalModuleNameRelative(name)) { + watchFailedLookupLocationOfResolution(resolution); + } + else { + nonRelativeExternalModuleResolutions.add(name, resolution); + } + } } + } - if (resolution.refCount !== undefined) { - resolution.refCount++; - return; - } + function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) { + Debug.assert(!!resolution.refCount); - resolution.refCount = 1; const { failedLookupLocations } = resolution; let setAtRoot = false; for (const failedLookupLocation of failedLookupLocations) { @@ -480,6 +492,16 @@ namespace ts { } } + function setRefCountToUndefined(resolution: ResolutionWithFailedLookupLocations) { + resolution.refCount = undefined; + } + + function watchFailedLookupLocationOfNonRelativeModuleResolutions(resolutions: ResolutionWithFailedLookupLocations[], name: string) { + const updateResolution = resolutionHost.getCurrentProgram().getTypeChecker().tryFindAmbientModuleWithoutAugmentations(name) ? + setRefCountToUndefined : watchFailedLookupLocationOfResolution; + resolutions.forEach(updateResolution); + } + function setDirectoryWatcher(dir: string, dirPath: Path, nonRecursive?: boolean) { const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); if (dirWatcher) { @@ -492,11 +514,11 @@ namespace ts { } function stopWatchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) { - if (!resolution.failedLookupLocations || !resolution.failedLookupLocations.length) { + if (!resolution.refCount) { return; } - resolution.refCount!--; + resolution.refCount--; if (resolution.refCount) { return; } diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 751f5221167..695b9f8622f 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -304,7 +304,7 @@ namespace ts { const outputs: string[] = []; outputs.push(getOutputJavaScriptFileName(inputFileName, configFile)); - if (configFile.options.declaration && !fileExtensionIs(inputFileName, Extension.Json)) { + if (getEmitDeclarations(configFile.options) && !fileExtensionIs(inputFileName, Extension.Json)) { const dts = getOutputDeclarationFileName(inputFileName, configFile); outputs.push(dts); if (configFile.options.declarationMap) { @@ -320,7 +320,7 @@ namespace ts { } const outputs: string[] = []; outputs.push(project.options.outFile); - if (project.options.declaration) { + if (getEmitDeclarations(project.options)) { const dts = changeExtension(project.options.outFile, Extension.Dts); outputs.push(dts); if (project.options.declarationMap) { @@ -769,7 +769,10 @@ namespace ts { const program = createProgram(programOptions); // Don't emit anything in the presence of syntactic errors or options diagnostics - const syntaxDiagnostics = [...program.getOptionsDiagnostics(), ...program.getSyntacticDiagnostics()]; + const syntaxDiagnostics = [ + ...program.getOptionsDiagnostics(), + ...program.getConfigFileParsingDiagnostics(), + ...program.getSyntacticDiagnostics()]; if (syntaxDiagnostics.length) { resultFlags |= BuildResultFlags.SyntaxErrors; for (const diag of syntaxDiagnostics) { @@ -780,7 +783,7 @@ namespace ts { } // Don't emit .d.ts if there are decl file errors - if (program.getCompilerOptions().declaration) { + if (getEmitDeclarations(program.getCompilerOptions())) { const declDiagnostics = program.getDeclarationDiagnostics(); if (declDiagnostics.length) { resultFlags |= BuildResultFlags.DeclarationEmitErrors; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 32a64ee97fd..eb629dc5144 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2994,9 +2994,9 @@ namespace ts { */ /* @internal */ tryGetMemberInModuleExportsAndProperties(memberName: string, moduleSymbol: Symbol): Symbol | undefined; getApparentType(type: Type): Type; - getSuggestionForNonexistentProperty(name: Identifier | string, containingType: Type): string | undefined; - getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string | undefined; - getSuggestionForNonexistentExport(node: Identifier, target: Symbol): string | undefined; + /* @internal */ getSuggestionForNonexistentProperty(name: Identifier | string, containingType: Type): string | undefined; + /* @internal */ getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string | undefined; + /* @internal */ getSuggestionForNonexistentExport(node: Identifier, target: Symbol): string | undefined; getBaseConstraintOfType(type: Type): Type | undefined; getDefaultFromTypeParameter(type: Type): Type | undefined; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 2246586c357..c3b42d595d1 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -7273,7 +7273,7 @@ namespace ts { } export function getAreDeclarationMapsEnabled(options: CompilerOptions) { - return !!(options.declaration && options.declarationMap); + return !!(getEmitDeclarations(options) && options.declarationMap); } export function getAllowSyntheticDefaultImports(compilerOptions: CompilerOptions) { @@ -8442,4 +8442,10 @@ namespace ts { } export type Mutable = { -readonly [K in keyof T]: T[K] }; + + export function sliceAfter(arr: ReadonlyArray, value: T): ReadonlyArray { + const index = arr.indexOf(value); + Debug.assert(index !== -1); + return arr.slice(index); + } } diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 3465ef333a0..095de4cecb0 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1515,7 +1515,9 @@ Actual: ${stringify(fullActual)}`); "argumentCount", ]; for (const key in options) { - ts.Debug.assert(ts.contains(allKeys, key)); + if (!ts.contains(allKeys, key)) { + ts.Debug.fail("Unexpected key " + key); + } } } @@ -3367,7 +3369,7 @@ Actual: ${stringify(fullActual)}`); this.languageServiceAdapterHost.renameFileOrDirectory(oldPath, newPath); this.languageService.cleanupSemanticCache(); - const pathUpdater = ts.getPathUpdater(oldPath, newPath, ts.createGetCanonicalFileName(/*useCaseSensitiveFileNames*/ false)); + const pathUpdater = ts.getPathUpdater(oldPath, newPath, ts.createGetCanonicalFileName(/*useCaseSensitiveFileNames*/ false), /*sourceMapper*/ undefined); test(renameKeys(newFileContents, key => pathUpdater(key) || key), "with file moved"); } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 761be1ae999..fa9c88d3ffa 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -155,7 +155,7 @@ namespace Harness.LanguageService { this.vfs.mkdirpSync(ts.getDirectoryPath(newPath)); this.vfs.renameSync(oldPath, newPath); - const updater = ts.getPathUpdater(oldPath, newPath, ts.createGetCanonicalFileName(this.useCaseSensitiveFileNames())); + const updater = ts.getPathUpdater(oldPath, newPath, ts.createGetCanonicalFileName(this.useCaseSensitiveFileNames()), /*sourceMapper*/ undefined); this.scriptInfos.forEach((scriptInfo, key) => { const newFileName = updater(key); if (newFileName !== undefined) { diff --git a/src/server/session.ts b/src/server/session.ts index abb20371b95..8f32cc7c68b 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -286,6 +286,16 @@ namespace ts.server { : deduplicate(outputs, areEqual); } + function combineProjectOutputFromEveryProject(projectService: ProjectService, action: (project: Project) => ReadonlyArray, areEqual: (a: T, b: T) => boolean) { + const outputs: T[] = []; + projectService.forEachProject(project => { + if (project.isOrphan() || !project.languageServiceEnabled) return; + const theseOutputs = action(project); + outputs.push(...theseOutputs.filter(output => !outputs.some(o => areEqual(o, output)))); + }); + return outputs; + } + function combineProjectOutputWhileOpeningReferencedProjects( projects: Projects, projectService: ProjectService, @@ -1749,19 +1759,11 @@ namespace ts.server { const newPath = toNormalizedPath(args.newFilePath); const formatOptions = this.getHostFormatOptions(); const preferences = this.getHostPreferences(); - - const changes: (protocol.FileCodeEdits | FileTextChanges)[] = []; - this.projectService.forEachProject(project => { - if (project.isOrphan() || !project.languageServiceEnabled) return; - for (const fileTextChanges of project.getLanguageService().getEditsForFileRename(oldPath, newPath, formatOptions, preferences)) { - // Subsequent projects may make conflicting edits to the same file -- just go with the first. - if (!changes.some(f => f.fileName === fileTextChanges.fileName)) { - changes.push(simplifiedResult ? this.mapTextChangeToCodeEdit(project, fileTextChanges) : fileTextChanges); - } - } - }); - - return changes as ReadonlyArray | ReadonlyArray; + const changes = combineProjectOutputFromEveryProject( + this.projectService, + project => project.getLanguageService().getEditsForFileRename(oldPath, newPath, formatOptions, preferences), + (a, b) => a.fileName === b.fileName); + return simplifiedResult ? changes.map(c => this.mapTextChangeToCodeEditUsingScriptInfo(c)) : changes; } private getCodeFixes(args: protocol.CodeFixRequestArgs, simplifiedResult: boolean): ReadonlyArray | ReadonlyArray | undefined { @@ -1835,8 +1837,15 @@ namespace ts.server { } private mapTextChangeToCodeEdit(project: Project, change: FileTextChanges): protocol.FileCodeEdits { - const path = normalizedPathToPath(toNormalizedPath(change.fileName), this.host.getCurrentDirectory(), fileName => this.getCanonicalFileName(fileName)); - return mapTextChangesToCodeEdits(change, project.getSourceFileOrConfigFile(path)); + return mapTextChangesToCodeEdits(change, project.getSourceFileOrConfigFile(this.normalizePath(change.fileName))); + } + + private mapTextChangeToCodeEditUsingScriptInfo(change: FileTextChanges): protocol.FileCodeEdits { + return mapTextChangesToCodeEditsUsingScriptInfo(change, this.projectService.getScriptInfo(this.normalizePath(change.fileName))); + } + + private normalizePath(fileName: string) { + return normalizedPathToPath(toNormalizedPath(fileName), this.host.getCurrentDirectory(), fileName => this.getCanonicalFileName(fileName)); } private convertTextChangeToCodeEdit(change: TextChange, scriptInfo: ScriptInfo): protocol.CodeEdit { @@ -2361,6 +2370,13 @@ namespace ts.server { } } + function mapTextChangesToCodeEditsUsingScriptInfo(textChanges: FileTextChanges, scriptInfo: ScriptInfo | undefined): protocol.FileCodeEdits { + Debug.assert(!!textChanges.isNewFile === !scriptInfo); + return scriptInfo + ? { fileName: textChanges.fileName, textChanges: textChanges.textChanges.map(textChange => convertTextChangeToCodeEditUsingScriptInfo(textChange, scriptInfo)) } + : convertNewFileTextChangeToCodeEdit(textChanges); + } + function convertTextChangeToCodeEdit(change: TextChange, sourceFile: SourceFile): protocol.CodeEdit { return { start: convertToLocation(sourceFile.getLineAndCharacterOfPosition(change.span.start)), @@ -2369,6 +2385,10 @@ namespace ts.server { }; } + function convertTextChangeToCodeEditUsingScriptInfo(change: TextChange, scriptInfo: ScriptInfo) { + return { start: scriptInfo.positionToLineOffset(change.span.start), end: scriptInfo.positionToLineOffset(textSpanEnd(change.span)), newText: change.newText }; + } + function convertNewFileTextChangeToCodeEdit(textChanges: FileTextChanges): protocol.FileCodeEdits { Debug.assert(textChanges.textChanges.length === 1); const change = first(textChanges.textChanges); diff --git a/src/services/codefixes/fixUnreachableCode.ts b/src/services/codefixes/fixUnreachableCode.ts index 1c6c53efe8e..4fdb2c2016f 100644 --- a/src/services/codefixes/fixUnreachableCode.ts +++ b/src/services/codefixes/fixUnreachableCode.ts @@ -5,14 +5,14 @@ namespace ts.codefix { registerCodeFix({ errorCodes, getCodeActions(context) { - const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, context.span.start)); + const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, context.span.start, context.span.length)); return [createCodeFixAction(fixId, changes, Diagnostics.Remove_unreachable_code, fixId, Diagnostics.Remove_all_unreachable_code)]; }, fixIds: [fixId], - getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => doChange(changes, diag.file, diag.start)), + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => doChange(changes, diag.file, diag.start, diag.length)), }); - function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, start: number): void { + function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, start: number, length: number): void { const token = getTokenAtPosition(sourceFile, start); const statement = findAncestor(token, isStatement)!; Debug.assert(statement.getStart(sourceFile) === token.getStart(sourceFile)); @@ -36,7 +36,9 @@ namespace ts.codefix { break; default: if (isBlock(statement.parent)) { - split(sliceAfter(statement.parent.statements, statement), shouldRemove, (start, end) => changes.deleteNodeRange(sourceFile, start, end)); + const end = start + length; + const lastStatement = Debug.assertDefined(lastWhere(sliceAfter(statement.parent.statements, statement), s => s.pos < end)); + changes.deleteNodeRange(sourceFile, statement, lastStatement); } else { changes.delete(sourceFile, statement); @@ -44,35 +46,12 @@ namespace ts.codefix { } } - function shouldRemove(s: Statement): boolean { - // Don't remove statements that can validly be used before they appear. - return !isFunctionDeclaration(s) && !isPurelyTypeDeclaration(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 hasModifier(s, ModifierFlags.Const); - default: - return false; + function lastWhere(a: ReadonlyArray, pred: (value: T) => boolean): T | undefined { + let last: T | undefined; + for (const value of a) { + if (!pred(value)) break; + last = value; } - } - - function sliceAfter(arr: ReadonlyArray, value: T): ReadonlyArray { - const index = arr.indexOf(value); - Debug.assert(index !== -1); - return arr.slice(index); - } - - // Calls 'cb' with the start and end of each range where 'pred' is true. - function split(arr: ReadonlyArray, pred: (t: T) => boolean, cb: (start: T, end: T) => void): void { - getRangesWhere(arr, pred, (start, afterEnd) => cb(arr[start], arr[afterEnd - 1])); + return last; } } diff --git a/src/services/completions.ts b/src/services/completions.ts index c049c1e4dae..8939fea2012 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -2,12 +2,20 @@ namespace ts.Completions { export type Log = (message: string) => void; - type SymbolOriginInfo = { type: "this-type" } | { type: "symbol-member" } | SymbolOriginInfoExport; + const enum SymbolOriginInfoKind { ThisType, SymbolMemberNoExport, SymbolMemberExport, Export } + type SymbolOriginInfo = { kind: SymbolOriginInfoKind.ThisType } | { kind: SymbolOriginInfoKind.SymbolMemberNoExport } | SymbolOriginInfoExport; interface SymbolOriginInfoExport { - type: "export"; + kind: SymbolOriginInfoKind.SymbolMemberExport | SymbolOriginInfoKind.Export; moduleSymbol: Symbol; isDefaultExport: boolean; } + function originIsSymbolMember(origin: SymbolOriginInfo): boolean { + return origin.kind === SymbolOriginInfoKind.SymbolMemberExport || origin.kind === SymbolOriginInfoKind.SymbolMemberNoExport; + } + function originIsExport(origin: SymbolOriginInfo): origin is SymbolOriginInfoExport { + return origin.kind === SymbolOriginInfoKind.SymbolMemberExport || origin.kind === SymbolOriginInfoKind.Export; + } + /** * Map from symbol id -> SymbolOriginInfo. * Only populated for symbols that come from other modules. @@ -214,12 +222,12 @@ namespace ts.Completions { let insertText: string | undefined; let replacementSpan: TextSpan | undefined; - if (origin && origin.type === "this-type") { + if (origin && origin.kind === SymbolOriginInfoKind.ThisType) { insertText = needsConvertPropertyAccess ? `this[${quote(name, preferences)}]` : `this.${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 ((origin && origin.type === "symbol-member" || needsConvertPropertyAccess) && propertyAccessToConvert) { + else if ((origin && originIsSymbolMember(origin) || needsConvertPropertyAccess) && propertyAccessToConvert) { insertText = needsConvertPropertyAccess ? `[${quote(name, preferences)}]` : `[${name}]`; const dot = findChildOfKind(propertyAccessToConvert, SyntaxKind.DotToken, sourceFile)!; // If the text after the '.' starts with this name, write over it. Else, add new text. @@ -253,7 +261,7 @@ namespace ts.Completions { kindModifiers: SymbolDisplay.getSymbolModifiers(symbol), sortText: "0", source: getSourceFromOrigin(origin), - hasAction: trueOrUndefined(!!origin && origin.type === "export"), + hasAction: trueOrUndefined(!!origin && originIsExport(origin)), isRecommended: trueOrUndefined(isRecommendedCompletionMatch(symbol, recommendedCompletion, typeChecker)), insertText, replacementSpan, @@ -283,7 +291,7 @@ namespace ts.Completions { } function getSourceFromOrigin(origin: SymbolOriginInfo | undefined): string | undefined { - return origin && origin.type === "export" ? stripQuotes(origin.moduleSymbol.name) : undefined; + return origin && originIsExport(origin) ? stripQuotes(origin.moduleSymbol.name) : undefined; } function getCompletionEntriesFromSymbols( @@ -529,7 +537,7 @@ namespace ts.Completions { } function getSymbolName(symbol: Symbol, origin: SymbolOriginInfo | undefined, target: ScriptTarget): string { - return origin && origin.type === "export" && origin.isDefaultExport && symbol.escapedName === InternalSymbolName.Default + return origin && originIsExport(origin) && origin.isDefaultExport && symbol.escapedName === InternalSymbolName.Default // Name of "export default foo;" is "foo". Name of "export default 0" is the filename converted to camelCase. ? firstDefined(symbol.declarations, d => isExportAssignment(d) && isIdentifier(d.expression) ? d.expression.text : undefined) || codefix.moduleSymbolToValidIdentifier(origin.moduleSymbol, target) @@ -648,7 +656,7 @@ namespace ts.Completions { preferences: UserPreferences, ): CodeActionsAndSourceDisplay { const symbolOriginInfo = symbolToOriginInfoMap[getSymbolId(symbol)]; - if (!symbolOriginInfo || symbolOriginInfo.type !== "export") { + if (!symbolOriginInfo || !originIsExport(symbolOriginInfo)) { return { codeActions: undefined, sourceDisplay: undefined }; } @@ -1124,7 +1132,9 @@ namespace ts.Completions { const firstAccessibleSymbol = nameSymbol && getFirstSymbolInChain(nameSymbol, contextToken, typeChecker); if (firstAccessibleSymbol && !symbolToOriginInfoMap[getSymbolId(firstAccessibleSymbol)]) { symbols.push(firstAccessibleSymbol); - symbolToOriginInfoMap[getSymbolId(firstAccessibleSymbol)] = { type: "symbol-member" }; + const moduleSymbol = firstAccessibleSymbol.parent; + symbolToOriginInfoMap[getSymbolId(firstAccessibleSymbol)] = + !moduleSymbol || !isExternalModuleSymbol(moduleSymbol) ? { kind: SymbolOriginInfoKind.SymbolMemberNoExport } : { kind: SymbolOriginInfoKind.SymbolMemberExport, moduleSymbol, isDefaultExport: false }; } } else { @@ -1222,7 +1232,7 @@ namespace ts.Completions { const thisType = typeChecker.tryGetThisTypeAt(scopeNode); if (thisType) { for (const symbol of getPropertiesForCompletion(thisType, typeChecker)) { - symbolToOriginInfoMap[getSymbolId(symbol)] = { type: "this-type" }; + symbolToOriginInfoMap[getSymbolId(symbol)] = { kind: SymbolOriginInfoKind.ThisType }; symbols.push(symbol); } } @@ -1374,7 +1384,7 @@ namespace ts.Completions { symbol = getLocalSymbolForExportDefault(symbol) || symbol; } - const origin: SymbolOriginInfo = { type: "export", moduleSymbol, isDefaultExport }; + const origin: SymbolOriginInfoExport = { kind: SymbolOriginInfoKind.Export, moduleSymbol, isDefaultExport }; if (detailsEntryId || stringContainsCharactersInOrder(getSymbolName(symbol, origin, target).toLowerCase(), tokenTextLowerCase)) { symbols.push(symbol); symbolToOriginInfoMap[getSymbolId(symbol)] = origin; diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 6bc55f54ff7..35da81535fe 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -234,7 +234,8 @@ namespace ts.FindAllReferences.Core { export function getReferencedSymbolsForNode(position: number, node: Node, program: Program, sourceFiles: ReadonlyArray, cancellationToken: CancellationToken, options: Options = {}, sourceFilesSet: ReadonlyMap = arrayToSet(sourceFiles, f => f.fileName)): SymbolAndEntries[] | undefined { if (isSourceFile(node)) { const reference = GoToDefinition.getReferenceAtPosition(node, position, program); - return reference && getReferencedSymbolsForModule(program, program.getTypeChecker().getMergedSymbol(reference.file.symbol), /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet); + const moduleSymbol = reference && program.getTypeChecker().getMergedSymbol(reference.file.symbol); + return moduleSymbol && getReferencedSymbolsForModule(program, moduleSymbol, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet); } if (!options.implementations) { @@ -703,7 +704,7 @@ namespace ts.FindAllReferences.Core { - 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 && !((parent!.flags & SymbolFlags.Module) && isExternalModuleSymbol(parent!) && !parent!.globalExports)) { + if (exposedByParent && !(isExternalModuleSymbol(parent!) && !parent!.globalExports)) { return undefined; } diff --git a/src/services/getEditsForFileRename.ts b/src/services/getEditsForFileRename.ts index bfb737ef03c..932c777907e 100644 --- a/src/services/getEditsForFileRename.ts +++ b/src/services/getEditsForFileRename.ts @@ -1,10 +1,18 @@ /* @internal */ namespace ts { - export function getEditsForFileRename(program: Program, oldFileOrDirPath: string, newFileOrDirPath: string, host: LanguageServiceHost, formatContext: formatting.FormatContext, preferences: UserPreferences): ReadonlyArray { + export function getEditsForFileRename( + program: Program, + oldFileOrDirPath: string, + newFileOrDirPath: string, + host: LanguageServiceHost, + formatContext: formatting.FormatContext, + preferences: UserPreferences, + sourceMapper: SourceMapper, + ): ReadonlyArray { const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host); const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); - const oldToNew = getPathUpdater(oldFileOrDirPath, newFileOrDirPath, getCanonicalFileName); - const newToOld = getPathUpdater(newFileOrDirPath, oldFileOrDirPath, getCanonicalFileName); + const oldToNew = getPathUpdater(oldFileOrDirPath, newFileOrDirPath, getCanonicalFileName, sourceMapper); + const newToOld = getPathUpdater(newFileOrDirPath, oldFileOrDirPath, getCanonicalFileName, sourceMapper); return textChanges.ChangeTracker.with({ host, formatContext }, changeTracker => { updateTsconfigFiles(program, changeTracker, oldToNew, newFileOrDirPath, host.getCurrentDirectory(), useCaseSensitiveFileNames); updateImports(program, changeTracker, oldToNew, newToOld, host, getCanonicalFileName, preferences); @@ -14,13 +22,27 @@ namespace ts { /** 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): PathUpdater { + export function getPathUpdater(oldFileOrDirPath: string, newFileOrDirPath: string, getCanonicalFileName: GetCanonicalFileName, sourceMapper: SourceMapper | undefined): PathUpdater { const canonicalOldPath = getCanonicalFileName(oldFileOrDirPath); return path => { - if (getCanonicalFileName(path) === canonicalOldPath) return newFileOrDirPath; - const suffix = tryRemoveDirectoryPrefix(path, canonicalOldPath, getCanonicalFileName); - return suffix === undefined ? undefined : newFileOrDirPath + "/" + suffix; + const originalPath = sourceMapper && sourceMapper.tryGetOriginalLocation({ fileName: path, position: 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; + } + } + + // 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, newFileOrDirPath: string, currentDirectory: string, useCaseSensitiveFileNames: boolean): void { diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index bae6bc7222c..9d2c61a1a7d 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -183,6 +183,26 @@ namespace ts.OutliningElementsCollector { return spanForObjectOrArrayLiteral(n); case SyntaxKind.ArrayLiteralExpression: return spanForObjectOrArrayLiteral(n, SyntaxKind.OpenBracketToken); + case SyntaxKind.JsxElement: + return spanForJSXElement(n); + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxOpeningElement: + return spanForJSXAttributes((n).attributes); + } + + 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 spanForJSXAttributes(node: JsxAttributes): OutliningSpan | undefined { + if (node.properties.length === 0) { + return undefined; + } + + return createOutliningSpanFromBounds(node.getStart(sourceFile), node.getEnd(), OutliningSpanKind.Code); } function spanForObjectOrArrayLiteral(node: Node, open: SyntaxKind.OpenBraceToken | SyntaxKind.OpenBracketToken = SyntaxKind.OpenBraceToken): OutliningSpan | undefined { diff --git a/src/services/services.ts b/src/services/services.ts index d294f53830c..ceda2a744a2 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1537,7 +1537,8 @@ namespace ts { } function getDocumentHighlights(fileName: string, position: number, filesToSearch: ReadonlyArray): DocumentHighlights[] | undefined { - Debug.assert(filesToSearch.some(f => normalizePath(f) === fileName)); + const normalizedFileName = normalizePath(fileName); + Debug.assert(filesToSearch.some(f => normalizePath(f) === normalizedFileName)); synchronizeHostData(); const sourceFilesToSearch = map(filesToSearch, f => Debug.assertDefined(program.getSourceFile(f))); const sourceFile = getValidSourceFile(fileName); @@ -1812,7 +1813,7 @@ namespace ts { } function getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): ReadonlyArray { - return ts.getEditsForFileRename(getProgram()!, oldFilePath, newFilePath, host, formatting.getFormatContext(formatOptions), preferences); + return ts.getEditsForFileRename(getProgram()!, oldFilePath, newFilePath, host, formatting.getFormatContext(formatOptions), preferences, sourceMapper); } function applyCodeActionCommand(action: CodeActionCommand): Promise; @@ -1883,11 +1884,16 @@ namespace ts { if (!token) return undefined; const element = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningElement(token.parent) ? token.parent.parent : isJsxText(token) ? token.parent : undefined; - if (element && !tagNamesAreEquivalent(element.openingElement.tagName, element.closingElement.tagName)) { + if (element && isUnclosedTag(element)) { return { newText: `` }; } } + function isUnclosedTag({ openingElement, closingElement, parent }: JsxElement): boolean { + return !tagNamesAreEquivalent(openingElement.tagName, closingElement.tagName) || + isJsxElement(parent) && tagNamesAreEquivalent(openingElement.tagName, parent.openingElement.tagName) && isUnclosedTag(parent); + } + function getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined { const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); const range = formatting.getRangeOfEnclosingComment(sourceFile, position); diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index 2884f7a7a0c..491c905785e 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -29,9 +29,12 @@ namespace ts.SignatureHelp { return undefined; } - if (shouldCarefullyCheckContext(triggerReason)) { - // In the middle of a string, don't provide signature help unless the user explicitly requested it. - if (isInString(sourceFile, position, startingToken)) { + // Only need to be careful if the user typed a character and signature help wasn't showing. + const shouldCarefullyCheckContext = !!triggerReason && triggerReason.kind === "characterTyped"; + + // Bail out quickly in the middle of a string or comment, don't provide signature help unless the user explicitly requested it. + if (shouldCarefullyCheckContext) { + if (isInString(sourceFile, position, startingToken) || isInComment(sourceFile, position)) { return undefined; } } @@ -41,8 +44,8 @@ namespace ts.SignatureHelp { cancellationToken.throwIfCancellationRequested(); - // Semantic filtering of signature help - const candidateInfo = getCandidateInfo(argumentInfo, typeChecker); + // Extra syntactic and semantic filtering of signature help + const candidateInfo = getCandidateInfo(argumentInfo, typeChecker, sourceFile, startingToken, shouldCarefullyCheckContext); cancellationToken.throwIfCancellationRequested(); if (!candidateInfo) { @@ -57,24 +60,57 @@ namespace ts.SignatureHelp { return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => createSignatureHelpItems(candidateInfo.candidates, candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker)); } - function shouldCarefullyCheckContext(reason: SignatureHelpTriggerReason | undefined) { - // Only need to be careful if the user typed a character and signature help wasn't showing. - return !!reason && reason.kind === "characterTyped"; - } + function getCandidateInfo( + argumentInfo: ArgumentListInfo, checker: TypeChecker, sourceFile: SourceFile, startingToken: Node, onlyUseSyntacticOwners: boolean): + { readonly candidates: ReadonlyArray, readonly resolvedSignature: Signature } | undefined { - function getCandidateInfo(argumentInfo: ArgumentListInfo, checker: TypeChecker): { readonly candidates: ReadonlyArray, readonly resolvedSignature: Signature } | undefined { const { invocation } = argumentInfo; if (invocation.kind === InvocationKind.Call) { + if (onlyUseSyntacticOwners) { + if (isCallOrNewExpression(invocation.node)) { + const invocationChildren = invocation.node.getChildren(sourceFile); + switch (startingToken.kind) { + case SyntaxKind.OpenParenToken: + if (!contains(invocationChildren, startingToken)) { + return undefined; + } + break; + case SyntaxKind.CommaToken: + const containingList = findContainingList(startingToken); + if (!containingList || !contains(invocationChildren, findContainingList(startingToken))) { + return undefined; + } + break; + case SyntaxKind.LessThanToken: + if (!lessThanFollowsCalledExpression(startingToken, sourceFile, invocation.node.expression)) { + return undefined; + } + break; + default: + return undefined; + } + } + else { + return undefined; + } + } + const candidates: Signature[] = []; const resolvedSignature = checker.getResolvedSignature(invocation.node, candidates, argumentInfo.argumentCount)!; // TODO: GH#18217 return candidates.length === 0 ? undefined : { candidates, resolvedSignature }; } - else { + else if (invocation.kind === InvocationKind.TypeArgs) { + if (onlyUseSyntacticOwners && !lessThanFollowsCalledExpression(startingToken, sourceFile, invocation.called)) { + return undefined; + } const type = checker.getTypeAtLocation(invocation.called)!; // TODO: GH#18217 const signatures = isNewExpression(invocation.called.parent) ? type.getConstructSignatures() : type.getCallSignatures(); const candidates = signatures.filter(candidate => !!candidate.typeParameters && candidate.typeParameters.length >= argumentInfo.argumentCount); return candidates.length === 0 ? undefined : { candidates, resolvedSignature: first(candidates) }; } + else { + Debug.assertNever(invocation); + } } function createJavaScriptSignatureHelpItems(argumentInfo: ArgumentListInfo, program: Program, cancellationToken: CancellationToken): SignatureHelpItems | undefined { @@ -107,6 +143,14 @@ namespace ts.SignatureHelp { } } + function lessThanFollowsCalledExpression(startingToken: Node, sourceFile: SourceFile, calledExpression: Expression) { + const precedingToken = Debug.assertDefined( + findPrecedingToken(startingToken.getFullStart(), sourceFile, startingToken.parent, /*excludeJsdoc*/ true) + ); + + return rangeContainsRange(calledExpression, precedingToken); + } + export interface ArgumentInfoForCompletions { readonly invocation: CallLikeExpression; readonly argumentIndex: number; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 4cb0672518a..dc213567830 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -679,7 +679,7 @@ namespace ts { let current: Node = sourceFile; outer: while (true) { // find the child that contains 'position' - for (const child of current.getChildren()) { + for (const child of current.getChildren(sourceFile)) { const start = allowPositionInLeadingTrivia ? child.getFullStart() : child.getStart(sourceFile, /*includeJsDoc*/ true); if (start > position) { // If this child begins after position, then all subsequent children will as well. @@ -1184,8 +1184,7 @@ namespace ts { /** True if the symbol is for an external module, as opposed to a namespace. */ export function isExternalModuleSymbol(moduleSymbol: Symbol): boolean { - Debug.assert(!!(moduleSymbol.flags & SymbolFlags.Module)); - return moduleSymbol.name.charCodeAt(0) === CharacterCodes.doubleQuote; + return !!(moduleSymbol.flags & SymbolFlags.Module) && moduleSymbol.name.charCodeAt(0) === CharacterCodes.doubleQuote; } /** Returns `true` the first time it encounters a node and `false` afterwards. */ diff --git a/src/testRunner/unittests/projectReferences.ts b/src/testRunner/unittests/projectReferences.ts index 070d26cccd5..4702c0c26ce 100644 --- a/src/testRunner/unittests/projectReferences.ts +++ b/src/testRunner/unittests/projectReferences.ts @@ -285,4 +285,23 @@ namespace ts { }); }); + describe("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) => { + assertHasError("Issues an error about the rootDir", program.getOptionsDiagnostics(), Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files); + assertHasError("Issues an error about the fileList", program.getOptionsDiagnostics(), Diagnostics.File_0_is_not_in_project_file_list_Projects_must_list_all_files_or_use_an_include_pattern); + }); + }); + }); + } diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 9ffc98c8c20..b989327a9c9 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -8486,7 +8486,7 @@ new C();` }); }); - it("when watching directories for failed lookup locations in amd resolution", () => { + describe("when watching directories for failed lookup locations in amd resolution", () => { const projectRoot = "/user/username/projects/project"; const nodeFile: File = { path: `${projectRoot}/src/typings/node.d.ts`, @@ -8530,19 +8530,35 @@ export const x = 10;` } }) }; - const files = [nodeFile, electronFile, srcFile, moduleFile, configFile, libFile]; - const host = createServerHost(files); - const service = createProjectService(host); - service.openClientFile(srcFile.path, srcFile.content, ScriptKind.TS, projectRoot); - checkProjectActualFiles(service.configuredProjects.get(configFile.path)!, files.map(f => f.path)); - checkWatchedFilesDetailed(host, mapDefined(files, f => f === srcFile ? undefined : f.path), 1); - checkWatchedDirectoriesDetailed(host, [`${projectRoot}`], 1, /*recursive*/ false); // failed lookup for fs - const expectedWatchedDirectories = createMap(); - expectedWatchedDirectories.set(`${projectRoot}/src`, 2); // Wild card and failed lookup - expectedWatchedDirectories.set(`${projectRoot}/somefolder`, 1); // failed lookup for somefolder/module2 - expectedWatchedDirectories.set(`${projectRoot}/node_modules`, 1); // failed lookup for with node_modules/@types/fs - expectedWatchedDirectories.set(`${projectRoot}/src/typings`, 1); // typeroot directory - checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ true); + + function verifyModuleResolution(useNodeFile: boolean) { + const files = [...(useNodeFile ? [nodeFile] : []), electronFile, srcFile, moduleFile, configFile, libFile]; + const host = createServerHost(files); + const service = createProjectService(host); + service.openClientFile(srcFile.path, srcFile.content, ScriptKind.TS, projectRoot); + checkProjectActualFiles(service.configuredProjects.get(configFile.path)!, files.map(f => f.path)); + checkWatchedFilesDetailed(host, mapDefined(files, f => f === srcFile ? undefined : f.path), 1); + if (useNodeFile) { + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); // since fs resolves to ambient module, shouldnt watch failed lookup + } + else { + checkWatchedDirectoriesDetailed(host, [`${projectRoot}`], 1, /*recursive*/ false); // failed lookup for fs + } + const expectedWatchedDirectories = createMap(); + expectedWatchedDirectories.set(`${projectRoot}/src`, 2); // Wild card and failed lookup + expectedWatchedDirectories.set(`${projectRoot}/somefolder`, 1); // failed lookup for somefolder/module2 + expectedWatchedDirectories.set(`${projectRoot}/node_modules`, 1); // failed lookup for with node_modules/@types/fs + expectedWatchedDirectories.set(`${projectRoot}/src/typings`, 1); // typeroot directory + checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ true); + } + + it("when resolves to ambient module", () => { + verifyModuleResolution(/*useNodeFile*/ true); + }); + + it("when resolution fails", () => { + verifyModuleResolution(/*useNodeFile*/ false); + }); }); }); @@ -9194,6 +9210,22 @@ export function Test2() { }); }); + + it("getEditsForFileRename", () => { + const { session, aTs, userTs } = 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" }, + ], + }, + ]); + }); }); function makeReferenceItem(file: File, isDefinition: boolean, text: string, lineText: string, options?: SpanFromSubstringOptions): protocol.ReferencesResponseItem { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 414a2a889f6..99fa48091b5 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1933,9 +1933,6 @@ declare namespace ts { getAmbientModules(): Symbol[]; tryGetMemberInModuleExports(memberName: string, moduleSymbol: Symbol): Symbol | undefined; getApparentType(type: Type): Type; - getSuggestionForNonexistentProperty(name: Identifier | string, containingType: Type): string | undefined; - getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string | undefined; - getSuggestionForNonexistentExport(node: Identifier, target: Symbol): string | undefined; getBaseConstraintOfType(type: Type): Type | undefined; getDefaultFromTypeParameter(type: Type): Type | undefined; /** @@ -8900,6 +8897,8 @@ declare namespace ts.server { private mapCodeFixAction; private mapTextChangesToCodeEdits; private mapTextChangeToCodeEdit; + private mapTextChangeToCodeEditUsingScriptInfo; + private normalizePath; private convertTextChangeToCodeEdit; private getBraceMatching; private getDiagnosticsForProject; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 50bca4eea4f..c3508ec353c 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1933,9 +1933,6 @@ declare namespace ts { getAmbientModules(): Symbol[]; tryGetMemberInModuleExports(memberName: string, moduleSymbol: Symbol): Symbol | undefined; getApparentType(type: Type): Type; - getSuggestionForNonexistentProperty(name: Identifier | string, containingType: Type): string | undefined; - getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string | undefined; - getSuggestionForNonexistentExport(node: Identifier, target: Symbol): string | undefined; getBaseConstraintOfType(type: Type): Type | undefined; getDefaultFromTypeParameter(type: Type): Type | undefined; /** diff --git a/tests/baselines/reference/cf.errors.txt b/tests/baselines/reference/cf.errors.txt index 8f5f82f8169..910ffe5d88d 100644 --- a/tests/baselines/reference/cf.errors.txt +++ b/tests/baselines/reference/cf.errors.txt @@ -14,7 +14,7 @@ tests/cases/compiler/cf.ts(36,13): error TS7027: Unreachable code detected. if (y==7) { continue L1; x=11; - ~ + ~~~~~ !!! error TS7027: Unreachable code detected. } if (y==3) { @@ -28,7 +28,7 @@ tests/cases/compiler/cf.ts(36,13): error TS7027: Unreachable code detected. if (y==20) { break; x=12; - ~ + ~~~~~ !!! error TS7027: Unreachable code detected. } } while (y<41); @@ -41,13 +41,13 @@ tests/cases/compiler/cf.ts(36,13): error TS7027: Unreachable code detected. L3: if (xTest : Symbol(Test, Decl(bug25434.js, 0, 0)) +>b : Symbol(b, Decl(bug25434.js, 1, 15)) + +Test(({ b = '5' } = {})); +>Test : Symbol(Test, Decl(bug25434.js, 0, 0)) +>b : Symbol(b, Decl(bug25434.js, 3, 7)) + diff --git a/tests/baselines/reference/checkDestructuringShorthandAssigment.types b/tests/baselines/reference/checkDestructuringShorthandAssigment.types new file mode 100644 index 00000000000..9eccb886051 --- /dev/null +++ b/tests/baselines/reference/checkDestructuringShorthandAssigment.types @@ -0,0 +1,17 @@ +=== tests/cases/compiler/bug25434.js === +// should not crash while checking +function Test({ b = '' } = {}) {} +>Test : ({ b }?: { [x: string]: any; }) => void +>b : string +>'' : "" +>{} : { b?: string; } + +Test(({ b = '5' } = {})); +>Test(({ b = '5' } = {})) : void +>Test : ({ b }?: { [x: string]: any; }) => void +>({ b = '5' } = {}) : { b?: any; } +>{ b = '5' } = {} : { b?: any; } +>{ b = '5' } : { [x: string]: any; b?: any; } +>b : any +>{} : { b?: any; } + diff --git a/tests/baselines/reference/errorElaboration.errors.txt b/tests/baselines/reference/errorElaboration.errors.txt index 94742d7d98b..57971cad269 100644 --- a/tests/baselines/reference/errorElaboration.errors.txt +++ b/tests/baselines/reference/errorElaboration.errors.txt @@ -2,9 +2,10 @@ tests/cases/compiler/errorElaboration.ts(12,5): error TS2345: Argument of type ' Type 'Container>' is not assignable to type 'Container>'. Type 'Ref' is not assignable to type 'Ref'. Type 'string' is not assignable to type 'number'. +tests/cases/compiler/errorElaboration.ts(17,11): error TS2322: Type '"bar"' is not assignable to type '"foo"'. -==== tests/cases/compiler/errorElaboration.ts (1 errors) ==== +==== tests/cases/compiler/errorElaboration.ts (2 errors) ==== // Repro for #5712 interface Ref { @@ -22,4 +23,13 @@ tests/cases/compiler/errorElaboration.ts(12,5): error TS2345: Argument of type ' !!! error TS2345: Type 'Container>' is not assignable to type 'Container>'. !!! error TS2345: Type 'Ref' is not assignable to type 'Ref'. !!! error TS2345: Type 'string' is not assignable to type 'number'. + + // Repro for #25498 + + function test(): {[A in "foo"]: A} { + return {foo: "bar"}; + ~~~ +!!! error TS2322: Type '"bar"' is not assignable to type '"foo"'. +!!! related TS6500 tests/cases/compiler/errorElaboration.ts:16:18: The expected type comes from property 'foo' which is declared here on type '{ foo: "foo"; }' + } \ No newline at end of file diff --git a/tests/baselines/reference/errorElaboration.js b/tests/baselines/reference/errorElaboration.js index eed8bbd7c6b..c1dd0a470d3 100644 --- a/tests/baselines/reference/errorElaboration.js +++ b/tests/baselines/reference/errorElaboration.js @@ -11,9 +11,19 @@ interface Container { declare function foo(x: () => Container>): void; let a: () => Container>; foo(a); + +// Repro for #25498 + +function test(): {[A in "foo"]: A} { + return {foo: "bar"}; +} //// [errorElaboration.js] // Repro for #5712 var a; foo(a); +// Repro for #25498 +function test() { + return { foo: "bar" }; +} diff --git a/tests/baselines/reference/errorElaboration.symbols b/tests/baselines/reference/errorElaboration.symbols index 72206e3caef..839b19c0599 100644 --- a/tests/baselines/reference/errorElaboration.symbols +++ b/tests/baselines/reference/errorElaboration.symbols @@ -38,3 +38,14 @@ foo(a); >foo : Symbol(foo, Decl(errorElaboration.ts, 8, 1)) >a : Symbol(a, Decl(errorElaboration.ts, 10, 3)) +// Repro for #25498 + +function test(): {[A in "foo"]: A} { +>test : Symbol(test, Decl(errorElaboration.ts, 11, 7)) +>A : Symbol(A, Decl(errorElaboration.ts, 15, 19)) +>A : Symbol(A, Decl(errorElaboration.ts, 15, 19)) + + return {foo: "bar"}; +>foo : Symbol(foo, Decl(errorElaboration.ts, 16, 10)) +} + diff --git a/tests/baselines/reference/errorElaboration.types b/tests/baselines/reference/errorElaboration.types index 96aaba6ce30..385ab481bb2 100644 --- a/tests/baselines/reference/errorElaboration.types +++ b/tests/baselines/reference/errorElaboration.types @@ -39,3 +39,16 @@ foo(a); >foo : (x: () => Container>) => void >a : () => Container> +// Repro for #25498 + +function test(): {[A in "foo"]: A} { +>test : () => { foo: "foo"; } +>A : A +>A : A + + return {foo: "bar"}; +>{foo: "bar"} : { foo: "bar"; } +>foo : "bar" +>"bar" : "bar" +} + diff --git a/tests/baselines/reference/jsFileCompilationBindErrors.errors.txt b/tests/baselines/reference/jsFileCompilationBindErrors.errors.txt index eeae7053333..64bd79d7fd9 100644 --- a/tests/baselines/reference/jsFileCompilationBindErrors.errors.txt +++ b/tests/baselines/reference/jsFileCompilationBindErrors.errors.txt @@ -15,7 +15,7 @@ tests/cases/compiler/a.js(11,9): error TS1100: Invalid use of 'arguments' in str function f() { return; return; // Error: Unreachable code detected. - ~~~~~~ + ~~~~~~~ !!! error TS7027: Unreachable code detected. } diff --git a/tests/baselines/reference/jsFileCompilationBindReachabilityErrors.errors.txt b/tests/baselines/reference/jsFileCompilationBindReachabilityErrors.errors.txt index a883e66e607..c2c572aa304 100644 --- a/tests/baselines/reference/jsFileCompilationBindReachabilityErrors.errors.txt +++ b/tests/baselines/reference/jsFileCompilationBindReachabilityErrors.errors.txt @@ -22,7 +22,7 @@ tests/cases/compiler/a.js(19,1): error TS7028: Unused label. function bar2() { } var x = 10; // error - ~~~ + ~~~~~~~~~~~ !!! error TS7027: Unreachable code detected. } diff --git a/tests/baselines/reference/jsdocTypeReferenceUseBeforeDef.symbols b/tests/baselines/reference/jsdocTypeReferenceUseBeforeDef.symbols new file mode 100644 index 00000000000..798c892f520 --- /dev/null +++ b/tests/baselines/reference/jsdocTypeReferenceUseBeforeDef.symbols @@ -0,0 +1,9 @@ +=== tests/cases/conformance/jsdoc/bug25097.js === +/** @type {C | null} */ +const c = null +>c : Symbol(c, Decl(bug25097.js, 1, 5)) + +class C { +>C : Symbol(C, Decl(bug25097.js, 1, 14)) +} + diff --git a/tests/baselines/reference/jsdocTypeReferenceUseBeforeDef.types b/tests/baselines/reference/jsdocTypeReferenceUseBeforeDef.types new file mode 100644 index 00000000000..0a79a140c7d --- /dev/null +++ b/tests/baselines/reference/jsdocTypeReferenceUseBeforeDef.types @@ -0,0 +1,10 @@ +=== tests/cases/conformance/jsdoc/bug25097.js === +/** @type {C | null} */ +const c = null +>c : C +>null : null + +class C { +>C : C +} + diff --git a/tests/baselines/reference/noCrashOnMixin.errors.txt b/tests/baselines/reference/noCrashOnMixin.errors.txt new file mode 100644 index 00000000000..01baff88417 --- /dev/null +++ b/tests/baselines/reference/noCrashOnMixin.errors.txt @@ -0,0 +1,29 @@ +tests/cases/compiler/noCrashOnMixin.ts(21,9): error TS2674: Constructor of class 'Abstract' is protected and only accessible within the class declaration. + + +==== tests/cases/compiler/noCrashOnMixin.ts (1 errors) ==== + class Abstract { + protected constructor() { + } + } + + class Concrete extends Abstract { + } + + type Constructor = new (...args: any[]) => T; + + function Mixin(Base: TBase) { + return class extends Base { + }; + } + + class Empty { + } + + class CrashTrigger extends Mixin(Empty) { + public trigger() { + new Concrete(); + ~~~~~~~~~~~~~~ +!!! error TS2674: Constructor of class 'Abstract' is protected and only accessible within the class declaration. + } + } \ No newline at end of file diff --git a/tests/baselines/reference/noCrashOnMixin.js b/tests/baselines/reference/noCrashOnMixin.js new file mode 100644 index 00000000000..049b61e8a1c --- /dev/null +++ b/tests/baselines/reference/noCrashOnMixin.js @@ -0,0 +1,75 @@ +//// [noCrashOnMixin.ts] +class Abstract { + protected constructor() { + } +} + +class Concrete extends Abstract { +} + +type Constructor = new (...args: any[]) => T; + +function Mixin(Base: TBase) { + return class extends Base { + }; +} + +class Empty { +} + +class CrashTrigger extends Mixin(Empty) { + public trigger() { + new Concrete(); + } +} + +//// [noCrashOnMixin.js] +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + } + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var Abstract = /** @class */ (function () { + function Abstract() { + } + return Abstract; +}()); +var Concrete = /** @class */ (function (_super) { + __extends(Concrete, _super); + function Concrete() { + return _super !== null && _super.apply(this, arguments) || this; + } + return Concrete; +}(Abstract)); +function Mixin(Base) { + return /** @class */ (function (_super) { + __extends(class_1, _super); + function class_1() { + return _super !== null && _super.apply(this, arguments) || this; + } + return class_1; + }(Base)); +} +var Empty = /** @class */ (function () { + function Empty() { + } + return Empty; +}()); +var CrashTrigger = /** @class */ (function (_super) { + __extends(CrashTrigger, _super); + function CrashTrigger() { + return _super !== null && _super.apply(this, arguments) || this; + } + CrashTrigger.prototype.trigger = function () { + new Concrete(); + }; + return CrashTrigger; +}(Mixin(Empty))); diff --git a/tests/baselines/reference/noCrashOnMixin.symbols b/tests/baselines/reference/noCrashOnMixin.symbols new file mode 100644 index 00000000000..3f99f99aa49 --- /dev/null +++ b/tests/baselines/reference/noCrashOnMixin.symbols @@ -0,0 +1,48 @@ +=== tests/cases/compiler/noCrashOnMixin.ts === +class Abstract { +>Abstract : Symbol(Abstract, Decl(noCrashOnMixin.ts, 0, 0)) + + protected constructor() { + } +} + +class Concrete extends Abstract { +>Concrete : Symbol(Concrete, Decl(noCrashOnMixin.ts, 3, 1)) +>Abstract : Symbol(Abstract, Decl(noCrashOnMixin.ts, 0, 0)) +} + +type Constructor = new (...args: any[]) => T; +>Constructor : Symbol(Constructor, Decl(noCrashOnMixin.ts, 6, 1)) +>T : Symbol(T, Decl(noCrashOnMixin.ts, 8, 17)) +>args : Symbol(args, Decl(noCrashOnMixin.ts, 8, 32)) +>T : Symbol(T, Decl(noCrashOnMixin.ts, 8, 17)) + +function Mixin(Base: TBase) { +>Mixin : Symbol(Mixin, Decl(noCrashOnMixin.ts, 8, 53)) +>TBase : Symbol(TBase, Decl(noCrashOnMixin.ts, 10, 15)) +>Constructor : Symbol(Constructor, Decl(noCrashOnMixin.ts, 6, 1)) +>Base : Symbol(Base, Decl(noCrashOnMixin.ts, 10, 42)) +>TBase : Symbol(TBase, Decl(noCrashOnMixin.ts, 10, 15)) + + return class extends Base { +>Base : Symbol(Base, Decl(noCrashOnMixin.ts, 10, 42)) + + }; +} + +class Empty { +>Empty : Symbol(Empty, Decl(noCrashOnMixin.ts, 13, 1)) +} + +class CrashTrigger extends Mixin(Empty) { +>CrashTrigger : Symbol(CrashTrigger, Decl(noCrashOnMixin.ts, 16, 1)) +>Mixin : Symbol(Mixin, Decl(noCrashOnMixin.ts, 8, 53)) +>Empty : Symbol(Empty, Decl(noCrashOnMixin.ts, 13, 1)) + + public trigger() { +>trigger : Symbol(CrashTrigger.trigger, Decl(noCrashOnMixin.ts, 18, 41)) + + new Concrete(); +>Concrete : Symbol(Concrete, Decl(noCrashOnMixin.ts, 3, 1)) + } +} diff --git a/tests/baselines/reference/noCrashOnMixin.types b/tests/baselines/reference/noCrashOnMixin.types new file mode 100644 index 00000000000..4d31e483b90 --- /dev/null +++ b/tests/baselines/reference/noCrashOnMixin.types @@ -0,0 +1,51 @@ +=== tests/cases/compiler/noCrashOnMixin.ts === +class Abstract { +>Abstract : Abstract + + protected constructor() { + } +} + +class Concrete extends Abstract { +>Concrete : Concrete +>Abstract : Abstract +} + +type Constructor = new (...args: any[]) => T; +>Constructor : Constructor +>T : T +>args : any[] +>T : T + +function Mixin(Base: TBase) { +>Mixin : >(Base: TBase) => { new (...args: any[]): (Anonymous class); prototype: Mixin.(Anonymous class); } & TBase +>TBase : TBase +>Constructor : Constructor +>Base : TBase +>TBase : TBase + + return class extends Base { +>class extends Base { } : { new (...args: any[]): (Anonymous class); prototype: Mixin.(Anonymous class); } & TBase +>Base : {} + + }; +} + +class Empty { +>Empty : Empty +} + +class CrashTrigger extends Mixin(Empty) { +>CrashTrigger : CrashTrigger +>Mixin(Empty) : Mixin.(Anonymous class) & Empty +>Mixin : >(Base: TBase) => { new (...args: any[]): (Anonymous class); prototype: Mixin.(Anonymous class); } & TBase +>Empty : typeof Empty + + public trigger() { +>trigger : () => void + + new Concrete(); +>new Concrete() : any +>Concrete : typeof Concrete + } +} diff --git a/tests/baselines/reference/reachabilityChecks1.errors.txt b/tests/baselines/reference/reachabilityChecks1.errors.txt index 0f1a9b7bb1e..041d10bfecd 100644 --- a/tests/baselines/reference/reachabilityChecks1.errors.txt +++ b/tests/baselines/reference/reachabilityChecks1.errors.txt @@ -10,13 +10,13 @@ tests/cases/compiler/reachabilityChecks1.ts(69,5): error TS7027: Unreachable cod ==== tests/cases/compiler/reachabilityChecks1.ts (7 errors) ==== while (true); var x = 1; - ~~~ + ~~~~~~~~~~ !!! error TS7027: Unreachable code detected. module A { while (true); let x; - ~~~ + ~~~~~~ !!! error TS7027: Unreachable code detected. } @@ -30,10 +30,12 @@ tests/cases/compiler/reachabilityChecks1.ts(69,5): error TS7027: Unreachable cod module A2 { while (true); module A { - ~~~~~~ -!!! error TS7027: Unreachable code detected. + ~~~~~~~~~~ var x = 1; + ~~~~~~~~~~~~~~~~~~ } + ~~~~~ +!!! error TS7027: Unreachable code detected. } module A3 { @@ -44,10 +46,12 @@ tests/cases/compiler/reachabilityChecks1.ts(69,5): error TS7027: Unreachable cod module A4 { while (true); module A { - ~~~~~~ -!!! error TS7027: Unreachable code detected. + ~~~~~~~~~~ const enum E { X } + ~~~~~~~~~~~~~~~~~~~~~~~~~~ } + ~~~~~ +!!! error TS7027: Unreachable code detected. } function f1(x) { @@ -63,9 +67,10 @@ tests/cases/compiler/reachabilityChecks1.ts(69,5): error TS7027: Unreachable cod function f2() { return; class A { - ~~~~~ -!!! error TS7027: Unreachable code detected. + ~~~~~~~~~ } + ~~~~~ +!!! error TS7027: Unreachable code detected. } module B { @@ -78,10 +83,12 @@ tests/cases/compiler/reachabilityChecks1.ts(69,5): error TS7027: Unreachable cod do { } while (true); enum E { - ~~~~ -!!! error TS7027: Unreachable code detected. + ~~~~~~~~ X = 1 + ~~~~~~~~~~~~~ } + ~~~~~ +!!! error TS7027: Unreachable code detected. } function f4() { @@ -89,10 +96,12 @@ tests/cases/compiler/reachabilityChecks1.ts(69,5): error TS7027: Unreachable cod throw new Error(); } const enum E { - ~~~~~ -!!! error TS7027: Unreachable code detected. + ~~~~~~~~~~~~~~ X = 1 + ~~~~~~~~~~~~~ } + ~~~~~ +!!! error TS7027: Unreachable code detected. } \ No newline at end of file diff --git a/tests/baselines/reference/reachabilityChecks2.errors.txt b/tests/baselines/reference/reachabilityChecks2.errors.txt index 9061effeb66..922f888fad3 100644 --- a/tests/baselines/reference/reachabilityChecks2.errors.txt +++ b/tests/baselines/reference/reachabilityChecks2.errors.txt @@ -6,12 +6,17 @@ tests/cases/compiler/reachabilityChecks2.ts(4,1): error TS7027: Unreachable code const enum E { X } module A4 { - ~~~~~~ -!!! error TS7027: Unreachable code detected. + ~~~~~~~~~~~ while (true); + ~~~~~~~~~~~~~~~~~ module A { + ~~~~~~~~~~~~~~ const enum E { X } + ~~~~~~~~~~~~~~~~~~~~~~~~~~ } + ~~~~~ } + ~ +!!! error TS7027: Unreachable code detected. \ No newline at end of file diff --git a/tests/baselines/reference/reachabilityChecks5.errors.txt b/tests/baselines/reference/reachabilityChecks5.errors.txt index 4dc7c1705f1..f8c7e6f4dbc 100644 --- a/tests/baselines/reference/reachabilityChecks5.errors.txt +++ b/tests/baselines/reference/reachabilityChecks5.errors.txt @@ -109,7 +109,7 @@ tests/cases/compiler/reachabilityChecks5.ts(122,13): error TS7027: Unreachable c } else { return 1; - ~~~~~~ + ~~~~~~~~~ !!! error TS7027: Unreachable code detected. } } @@ -124,7 +124,7 @@ tests/cases/compiler/reachabilityChecks5.ts(122,13): error TS7027: Unreachable c try { while (false) { return 1; - ~~~~~~ + ~~~~~~~~~ !!! error TS7027: Unreachable code detected. } } @@ -154,7 +154,7 @@ tests/cases/compiler/reachabilityChecks5.ts(122,13): error TS7027: Unreachable c break test; } while (true); x++; - ~ + ~~~~ !!! error TS7027: Unreachable code detected. } while (true); } diff --git a/tests/baselines/reference/reachabilityChecks6.errors.txt b/tests/baselines/reference/reachabilityChecks6.errors.txt index 9dadf3b2d6f..66744af3e03 100644 --- a/tests/baselines/reference/reachabilityChecks6.errors.txt +++ b/tests/baselines/reference/reachabilityChecks6.errors.txt @@ -106,7 +106,7 @@ tests/cases/compiler/reachabilityChecks6.ts(122,13): error TS7027: Unreachable c } else { return 1; - ~~~~~~ + ~~~~~~~~~ !!! error TS7027: Unreachable code detected. } } @@ -121,7 +121,7 @@ tests/cases/compiler/reachabilityChecks6.ts(122,13): error TS7027: Unreachable c try { while (false) { return 1; - ~~~~~~ + ~~~~~~~~~ !!! error TS7027: Unreachable code detected. } } @@ -151,7 +151,7 @@ tests/cases/compiler/reachabilityChecks6.ts(122,13): error TS7027: Unreachable c break test; } while (true); x++; - ~ + ~~~~ !!! error TS7027: Unreachable code detected. } while (true); } diff --git a/tests/baselines/reference/unreachableJavascriptChecked.errors.txt b/tests/baselines/reference/unreachableJavascriptChecked.errors.txt index c66b342177f..a4aee3b7805 100644 --- a/tests/baselines/reference/unreachableJavascriptChecked.errors.txt +++ b/tests/baselines/reference/unreachableJavascriptChecked.errors.txt @@ -1,10 +1,18 @@ tests/cases/compiler/unreachable.js(3,5): error TS7027: Unreachable code detected. +tests/cases/compiler/unreachable.js(6,5): error TS7027: Unreachable code detected. -==== tests/cases/compiler/unreachable.js (1 errors) ==== +==== tests/cases/compiler/unreachable.js (2 errors) ==== function unreachable() { - return 1; + return f(); return 2; - ~~~~~~ + ~~~~~~~~~ + return 3; + ~~~~~~~~~~~~~ !!! error TS7027: Unreachable code detected. - } \ No newline at end of file + function f() {} + return 4; + ~~~~~~~~~ +!!! error TS7027: Unreachable code detected. + } + \ No newline at end of file diff --git a/tests/baselines/reference/unreachableJavascriptChecked.js b/tests/baselines/reference/unreachableJavascriptChecked.js index 6bf264bf3d2..d12d4af6753 100644 --- a/tests/baselines/reference/unreachableJavascriptChecked.js +++ b/tests/baselines/reference/unreachableJavascriptChecked.js @@ -1,11 +1,18 @@ //// [unreachable.js] function unreachable() { - return 1; + return f(); return 2; -} + return 3; + function f() {} + return 4; +} + //// [unreachable.js] function unreachable() { - return 1; + return f(); return 2; + return 3; + function f() { } + return 4; } diff --git a/tests/baselines/reference/unreachableJavascriptChecked.symbols b/tests/baselines/reference/unreachableJavascriptChecked.symbols index f2db6a6a347..11610239f59 100644 --- a/tests/baselines/reference/unreachableJavascriptChecked.symbols +++ b/tests/baselines/reference/unreachableJavascriptChecked.symbols @@ -2,6 +2,14 @@ function unreachable() { >unreachable : Symbol(unreachable, Decl(unreachable.js, 0, 0)) - return 1; + return f(); +>f : Symbol(f, Decl(unreachable.js, 3, 13)) + return 2; + return 3; + function f() {} +>f : Symbol(f, Decl(unreachable.js, 3, 13)) + + return 4; } + diff --git a/tests/baselines/reference/unreachableJavascriptChecked.types b/tests/baselines/reference/unreachableJavascriptChecked.types index 266a9038875..d27af1d33d8 100644 --- a/tests/baselines/reference/unreachableJavascriptChecked.types +++ b/tests/baselines/reference/unreachableJavascriptChecked.types @@ -1,10 +1,21 @@ === tests/cases/compiler/unreachable.js === function unreachable() { ->unreachable : () => 1 | 2 +>unreachable : () => void | 2 | 3 | 4 - return 1; ->1 : 1 + return f(); +>f() : void +>f : () => void return 2; >2 : 2 + + return 3; +>3 : 3 + + function f() {} +>f : () => void + + return 4; +>4 : 4 } + diff --git a/tests/cases/compiler/checkDestructuringShorthandAssigment.ts b/tests/cases/compiler/checkDestructuringShorthandAssigment.ts new file mode 100644 index 00000000000..e3422aae753 --- /dev/null +++ b/tests/cases/compiler/checkDestructuringShorthandAssigment.ts @@ -0,0 +1,8 @@ +// @allowjs: true +// @checkjs: true +// @noEmit: true +// @Filename: bug25434.js +// should not crash while checking +function Test({ b = '' } = {}) {} + +Test(({ b = '5' } = {})); diff --git a/tests/cases/compiler/errorElaboration.ts b/tests/cases/compiler/errorElaboration.ts index 97ec0dde61c..87575f1df11 100644 --- a/tests/cases/compiler/errorElaboration.ts +++ b/tests/cases/compiler/errorElaboration.ts @@ -10,3 +10,9 @@ interface Container { declare function foo(x: () => Container>): void; let a: () => Container>; foo(a); + +// Repro for #25498 + +function test(): {[A in "foo"]: A} { + return {foo: "bar"}; +} diff --git a/tests/cases/compiler/noCrashOnMixin.ts b/tests/cases/compiler/noCrashOnMixin.ts new file mode 100644 index 00000000000..900e827fa46 --- /dev/null +++ b/tests/cases/compiler/noCrashOnMixin.ts @@ -0,0 +1,23 @@ +class Abstract { + protected constructor() { + } +} + +class Concrete extends Abstract { +} + +type Constructor = new (...args: any[]) => T; + +function Mixin(Base: TBase) { + return class extends Base { + }; +} + +class Empty { +} + +class CrashTrigger extends Mixin(Empty) { + public trigger() { + new Concrete(); + } +} \ No newline at end of file diff --git a/tests/cases/compiler/unreachableJavascriptChecked.ts b/tests/cases/compiler/unreachableJavascriptChecked.ts index afddcba6a23..3d9c906dc05 100644 --- a/tests/cases/compiler/unreachableJavascriptChecked.ts +++ b/tests/cases/compiler/unreachableJavascriptChecked.ts @@ -4,6 +4,9 @@ // @outDir: out // @allowUnreachableCode: false function unreachable() { - return 1; + return f(); return 2; -} \ No newline at end of file + return 3; + function f() {} + return 4; +} diff --git a/tests/cases/conformance/jsdoc/jsdocTypeReferenceUseBeforeDef.ts b/tests/cases/conformance/jsdoc/jsdocTypeReferenceUseBeforeDef.ts new file mode 100644 index 00000000000..93e735e1fa6 --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocTypeReferenceUseBeforeDef.ts @@ -0,0 +1,8 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: bug25097.js +/** @type {C | null} */ +const c = null +class C { +} diff --git a/tests/cases/fourslash/autoCloseTag.ts b/tests/cases/fourslash/autoCloseTag.ts index ba828f207a2..6ba0b0f209f 100644 --- a/tests/cases/fourslash/autoCloseTag.ts +++ b/tests/cases/fourslash/autoCloseTag.ts @@ -1,20 +1,51 @@ /// -// @Filename: /a.tsx +// Using separate files for each example to avoid unclosed JSX tags affecting other tests. + +// @Filename: /0.tsx ////const x =
/*0*/; + +// @Filename: /1.tsx ////const x =
foo/*1*/
; + +// @Filename: /2.tsx ////const x =
/*2*/; + +// @Filename: /3.tsx ////const x =
/*3*/; + +// @Filename: /4.tsx ////const x =
////

/*4*/ ////

////

; + +// @Filename: /5.tsx ////const x =
text /*5*/; +// @Filename: /6.tsx +////const x =
+////
/*6*/ +////
; + +// @Filename: /7.tsx +////const x =
+////

/*7*/ +////

; + +// @Filename: /8.tsx +////const x =
+////
/*8*/
+////
; + verify.jsxClosingTag({ 0: { newText: "
" }, 1: undefined, 2: undefined, 3: undefined, 4: { newText: "

" }, + 5: { newText: "
" }, + 6: { newText: "
" }, + 7: { newText: "

" }, + 8: undefined, }); diff --git a/tests/cases/fourslash/codeFixUnreachableCode.ts b/tests/cases/fourslash/codeFixUnreachableCode.ts index 5f70d51c1ee..9c9eea6dae4 100644 --- a/tests/cases/fourslash/codeFixUnreachableCode.ts +++ b/tests/cases/fourslash/codeFixUnreachableCode.ts @@ -2,29 +2,30 @@ ////function f() { //// return f(); -//// [|return|] 1; +//// [|return 1;|] //// function f() {} -//// return 2; +//// [|return 2;|] //// type T = number; //// interface I {} //// const enum E {} -//// enum E {} +//// [|enum E {}|] //// namespace N { export type T = number; } -//// namespace N { export const x: T = 0; } +//// [|namespace N { export const x: T = 0; }|] //// var x: I; -//// var y: T = 0; -//// E; N; x; y; +//// [|var y: T = 0; +//// E; N; x; y;|] ////} -verify.getSuggestionDiagnostics([{ +verify.getSuggestionDiagnostics(test.ranges().map((range): FourSlashInterface.Diagnostic => ({ message: "Unreachable code detected.", code: 7027, reportsUnnecessary: true, -}]); + range, +}))); -verify.codeFix({ - description: "Remove unreachable code", - index: 0, +verify.codeFixAll({ + fixId: "fixUnreachableCode", + fixAllDescription: "Remove all unreachable code", newFileContent: `function f() { return f(); diff --git a/tests/cases/fourslash/completionsUniqueSymbol_import.ts b/tests/cases/fourslash/completionsUniqueSymbol_import.ts index f15bc59abff..df3034e56fe 100644 --- a/tests/cases/fourslash/completionsUniqueSymbol_import.ts +++ b/tests/cases/fourslash/completionsUniqueSymbol_import.ts @@ -1,12 +1,17 @@ /// -// @Filename: /a.ts +// @noLib: true + +// @Filename: /globals.d.ts ////declare const Symbol: () => symbol; + +// @Filename: /a.ts ////const privateSym = Symbol(); ////export const publicSym = Symbol(); ////export interface I { //// [privateSym]: number; //// [publicSym]: number; +//// [defaultPublicSym]: number; //// n: number; ////} ////export const i: I; @@ -17,10 +22,21 @@ verify.completions({ marker: "", - // TODO: GH#25095 Should include `publicSym` - exact: "n", + exact: [ + "n", + { name: "publicSym", insertText: "[publicSym]", replacementSpan: test.ranges()[0], hasAction: true }, + ], preferences: { includeInsertTextCompletions: true, includeCompletionsForModuleExports: true, }, }); + +verify.applyCodeActionFromCompletion("", { + name: "publicSym", + source: "/a", + description: `Add 'publicSym' to existing import declaration from "./a"`, + newFileContent: +`import { i, publicSym } from "./a"; +i.;` +}); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 81cb387b73d..ddad4ca52a6 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -554,7 +554,6 @@ declare namespace FourSlashInterface { overloadsCount?: number; docComment?: string; text?: string; - name?: string; parameterName?: string; parameterSpan?: string; parameterDocComment?: string; diff --git a/tests/cases/fourslash/getJSXOutliningSpans.tsx b/tests/cases/fourslash/getJSXOutliningSpans.tsx new file mode 100644 index 00000000000..fa4f541b193 --- /dev/null +++ b/tests/cases/fourslash/getJSXOutliningSpans.tsx @@ -0,0 +1,33 @@ +////import React, { Component } from 'react'; +//// +////export class Home extends Component[| { +//// render()[| { +//// return ( +//// [|
+//// [|

Hello, world!

|] +//// [||] +////
+//// +////
|] +//// ); +//// }|] +////}|] + +verify.outliningSpansInCurrentFile(test.ranges(), "code"); diff --git a/tests/cases/fourslash/signatureHelpFilteredTriggerCharacters01.ts b/tests/cases/fourslash/signatureHelpFilteredTriggerCharacters01.ts deleted file mode 100644 index ed97ef020d0..00000000000 --- a/tests/cases/fourslash/signatureHelpFilteredTriggerCharacters01.ts +++ /dev/null @@ -1,23 +0,0 @@ -/// - -////function foo(x: T): T { -//// throw null; -////} -//// -////foo("/**/") - -goTo.marker(); -for (const triggerCharacter of ["<", "(", ","]) { - edit.insert(triggerCharacter); - verify.noSignatureHelpForTriggerReason({ - kind: "characterTyped", - triggerCharacter, - }); - verify.signatureHelpPresentForTriggerReason({ - kind: "retrigger", - triggerCharacter, - }); - edit.backspace(); -} -verify.signatureHelpPresentForTriggerReason(/*triggerReason*/ undefined); -verify.signatureHelpPresentForTriggerReason({ kind: "invoked" }); \ No newline at end of file diff --git a/tests/cases/fourslash/signatureHelpFilteredTriggers01.ts b/tests/cases/fourslash/signatureHelpFilteredTriggers01.ts new file mode 100644 index 00000000000..ebf5ba10f2d --- /dev/null +++ b/tests/cases/fourslash/signatureHelpFilteredTriggers01.ts @@ -0,0 +1,31 @@ +/// + +////function foo(x: T): T { +//// throw null; +////} +//// +////foo("/*1*/"); +////foo('/*2*/'); +////foo(` ${100}/*3*/`); +////foo(/* /*4*/ */); +////foo( +//// ///*5*/ +////); + +for (const marker of test.markers()) { + goTo.marker(marker); + for (const triggerCharacter of ["<", "(", ","]) { + edit.insert(triggerCharacter); + verify.noSignatureHelpForTriggerReason({ + kind: "characterTyped", + triggerCharacter, + }); + verify.signatureHelpPresentForTriggerReason({ + kind: "retrigger", + triggerCharacter, + }); + edit.backspace(); + } + verify.signatureHelpPresentForTriggerReason(/*triggerReason*/ undefined); + verify.signatureHelpPresentForTriggerReason({ kind: "invoked" }); +} diff --git a/tests/cases/fourslash/signatureHelpFilteredTriggers02.ts b/tests/cases/fourslash/signatureHelpFilteredTriggers02.ts new file mode 100644 index 00000000000..e193f919e05 --- /dev/null +++ b/tests/cases/fourslash/signatureHelpFilteredTriggers02.ts @@ -0,0 +1,36 @@ +/// + +////function foo(x: T): T { +//// throw null; +////} +//// +////foo(/*1*/""); +////foo(` ${100/*2*/}`); +////foo(/*3*/); +////foo(100 /*4*/) +////foo([/*5*/]) +////foo({ hello: "hello"/*6*/}) + +const charMap = { + 1: "(", + 2: ",", + 3: "(", + 4: "<", + 5: ",", + 6: ",", +} + +for (const markerName of Object.keys(charMap)) { + const triggerCharacter = charMap[markerName]; + goTo.marker(markerName); + edit.insert(triggerCharacter); + verify.noSignatureHelpForTriggerReason({ + kind: "characterTyped", + triggerCharacter, + }); + verify.signatureHelpPresentForTriggerReason({ + kind: "retrigger", + triggerCharacter, + }); + edit.backspace(triggerCharacter.length); +} diff --git a/tests/cases/fourslash/signatureHelpFilteredTriggers03.ts b/tests/cases/fourslash/signatureHelpFilteredTriggers03.ts new file mode 100644 index 00000000000..7b170823610 --- /dev/null +++ b/tests/cases/fourslash/signatureHelpFilteredTriggers03.ts @@ -0,0 +1,23 @@ +/// + +////declare class ViewJayEss { +//// constructor(obj: object); +////} +////new ViewJayEss({ +//// methods: { +//// sayHello/**/ +//// } +////}); + +goTo.marker(); +edit.insert("("); +verify.noSignatureHelpForTriggerReason({ + kind: "characterTyped", + triggerCharacter: "(", +}); + +edit.insert(") {},"); +verify.noSignatureHelpForTriggerReason({ + kind: "characterTyped", + triggerCharacter: ",", +}); diff --git a/tests/cases/fourslash/signatureHelpWithTriggers01.ts b/tests/cases/fourslash/signatureHelpWithTriggers01.ts new file mode 100644 index 00000000000..ae73b5fe000 --- /dev/null +++ b/tests/cases/fourslash/signatureHelpWithTriggers01.ts @@ -0,0 +1,31 @@ +/// + +////declare function foo(x: T, y: T): T; +//// +////foo/*1*//*2*/; +////foo(/*3*/100/*4*/); +////foo/*5*//*6*/(); + +const charMap = { + 1: "(", + 2: "<", + 3: ",", + 4: ",", + 5: "(", + 6: "<", +} + +for (const markerName of Object.keys(charMap)) { + const triggerCharacter = charMap[markerName]; + goTo.marker(markerName); + edit.insert(triggerCharacter); + verify.signatureHelpPresentForTriggerReason({ + kind: "characterTyped", + triggerCharacter, + }); + verify.signatureHelpPresentForTriggerReason({ + kind: "retrigger", + triggerCharacter, + }); + edit.backspace(triggerCharacter.length); +} diff --git a/tests/cases/fourslash/signatureHelpWithTriggers02.ts b/tests/cases/fourslash/signatureHelpWithTriggers02.ts new file mode 100644 index 00000000000..cb73237f8fd --- /dev/null +++ b/tests/cases/fourslash/signatureHelpWithTriggers02.ts @@ -0,0 +1,38 @@ +/// + +////declare function foo(x: T, y: T): T; +////declare function bar(x: U, y: U): U; +//// +////foo(bar/*1*/) + +goTo.marker("1"); + +edit.insert("("); +verify.signatureHelp({ + text: "bar(x: U, y: U): U", + triggerReason: { + kind: "characterTyped", + triggerCharacter: "(", + } +}); +edit.backspace(); + +edit.insert("<"); +verify.signatureHelp({ + text: "bar(x: U, y: U): U", + triggerReason: { + kind: "characterTyped", + triggerCharacter: "(", + } +}); +edit.backspace(); + +edit.insert(","); +verify.signatureHelp({ + text: "foo(x: (x: U, y: U) => U, y: (x: U, y: U) => U): (x: U, y: U) => U", + triggerReason: { + kind: "characterTyped", + triggerCharacter: "(", + } +}); +edit.backspace(); \ No newline at end of file diff --git a/tests/projects/outfile-concat/first/tsconfig.json b/tests/projects/outfile-concat/first/tsconfig.json index f71a8182585..8d63a0c4a06 100644 --- a/tests/projects/outfile-concat/first/tsconfig.json +++ b/tests/projects/outfile-concat/first/tsconfig.json @@ -6,7 +6,6 @@ "strict": false, "sourceMap": true, "declarationMap": true, - "declaration": true, "outFile": "./bin/first-output.js" }, "files": [