diff --git a/src/server/session.ts b/src/server/session.ts index dc0d4f42f5c79..b2c72a32b8b51 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1367,7 +1367,7 @@ namespace ts.server { emptyArray; } - private getSyntacticDiagnosticsSync(args: protocol.SyntacticDiagnosticsSyncRequestArgs): readonly protocol.Diagnostic[] | readonly protocol.DiagnosticWithLinePosition[] { + private getSyntacticDiagnosticsSync(args: protocol.SyntacticDiagnosticsSyncRequestArgs) { const { configFile } = this.getConfigFileAndProject(args); if (configFile) { // all the config file errors are reported as part of semantic check so nothing to report here @@ -1377,7 +1377,7 @@ namespace ts.server { return this.getDiagnosticsWorker(args, /*isSemantic*/ false, (project, file) => project.getLanguageService().getSyntacticDiagnostics(file), !!args.includeLinePosition); } - private getSemanticDiagnosticsSync(args: protocol.SemanticDiagnosticsSyncRequestArgs): readonly protocol.Diagnostic[] | readonly protocol.DiagnosticWithLinePosition[] { + private getSemanticDiagnosticsSync(args: protocol.SemanticDiagnosticsSyncRequestArgs) { const { configFile, project } = this.getConfigFileAndProject(args); if (configFile) { return this.getConfigFileDiagnostics(configFile, project!, !!args.includeLinePosition); // TODO: GH#18217 @@ -1385,7 +1385,7 @@ namespace ts.server { return this.getDiagnosticsWorker(args, /*isSemantic*/ true, (project, file) => project.getLanguageService().getSemanticDiagnostics(file).filter(d => !!d.file), !!args.includeLinePosition); } - private getSuggestionDiagnosticsSync(args: protocol.SuggestionDiagnosticsSyncRequestArgs): readonly protocol.Diagnostic[] | readonly protocol.DiagnosticWithLinePosition[] { + private getSuggestionDiagnosticsSync(args: protocol.SuggestionDiagnosticsSyncRequestArgs) { const { configFile } = this.getConfigFileAndProject(args); if (configFile) { // Currently there are no info diagnostics for config files. @@ -2198,7 +2198,25 @@ namespace ts.server { const scriptInfo = project.getScriptInfoForNormalizedPath(file)!; const { startPosition, endPosition } = this.getStartAndEndPosition(args, scriptInfo); - const codeActions = project.getLanguageService().getCodeFixesAtPosition(file, startPosition, endPosition, args.errorCodes, this.getFormatOptions(file), this.getPreferences(file)); + let codeActions: readonly CodeFixAction[]; + try { + codeActions = project.getLanguageService().getCodeFixesAtPosition(file, startPosition, endPosition, args.errorCodes, this.getFormatOptions(file), this.getPreferences(file)); + } + catch(e) { + const ls = project.getLanguageService(); + const existingDiagCodes = [ + ...ls.getSyntacticDiagnostics(file), + ...ls.getSemanticDiagnostics(file), + ...ls.getSuggestionDiagnostics(file) + ].map(d => + decodedTextSpanIntersectsWith(startPosition, endPosition - startPosition, d.start!, d.length!) + && d.code); + const badCode = args.errorCodes.find(c => !existingDiagCodes.includes(c)); + if (badCode !== undefined) { + e.message = `BADCLIENT: Bad error code, ${badCode} not found in range ${startPosition}..${endPosition} (found: ${existingDiagCodes.join(", ")}); could have caused this error:\n${e.message}`; + } + throw e; + } return simplifiedResult ? codeActions.map(codeAction => this.mapCodeFixAction(codeAction)) : codeActions; } diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index db7e0550e9a38..9c755729c20e8 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -237,8 +237,10 @@ namespace ts.codefix { if (isParameter(parent)) { tryDeleteParameter(changes, sourceFile, parent, checker, sourceFiles, program, cancellationToken, isFixAll); } - else if (!isFixAll || !(isIdentifier(token) && FindAllReferences.Core.isSymbolReferencedInFile(token, checker, sourceFile))) { - changes.delete(sourceFile, isImportClause(parent) ? token : isComputedPropertyName(parent) ? parent.parent : parent); + else if (!(isFixAll && isIdentifier(token) && FindAllReferences.Core.isSymbolReferencedInFile(token, checker, sourceFile))) { + const node = isImportClause(parent) ? token : isComputedPropertyName(parent) ? parent.parent : parent; + Debug.assert(node !== sourceFile, "should not delete whole source file"); + changes.delete(sourceFile, node); } } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index adbcfe68bef42..2425ae43d1271 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -1361,7 +1361,11 @@ namespace ts.textChanges { break; default: - if (isImportClause(node.parent) && node.parent.name === node) { + 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)) {